<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .title {
      text-align: center;
      font-weight: 900;
      font-size: 20px;
    }

    #output {
      display: flex;
      flex-direction: column;
      gap: 10px;
    }

    .card {
      width: 250px;
      background: rgb(44, 44, 44);
      border-radius: 4px;
      overflow: hidden;
    }

    .card__data {
      font-size: 0.8rem;
      display: flex;
      justify-content: space-between;
      border: 1px solid rgb(203, 203, 203);
    }

    .card__right {
      width: 60%;
      border-right: 1px solid rgb(203, 203, 203);
    }

    .card__left {
      width: 40%;
      text-align: end;
    }

    .item {
      padding: 3px 0;
      background-color: white;
    }

    .card__right .item {
      padding-left: 0.8em;
    }

    .card__left .item {
      padding-right: 0.8em;
    }

    .item:nth-child(even) {
      background: rgb(234, 235, 234);
    }
  </style>
</head>
<body>
  <div class="title">重写打印显示效果一般</div>
  <div class="title">按键盘上的 F12 可在控制台视图中查看消息</div><br /><br />
  <div id="output"></div>
</body>
<script>
  // 获取编辑器中的代码
  const userCode = ""
    // 创建一个函数来捕获 console 输出和其他错误
    ; (function () {
      // 创建一个用于存储所有输出的对象
      let outputs = [];
      let times = {}
      // 保存原始的 console 方法
      const originalConsoleMethods = {};
      ['log', 'info', 'warn', 'error', 'table', 'time', 'timeEnd'].forEach(method => {
        originalConsoleMethods[method] = console[method];
      });
      // 恢复原始的 console 方法
      function restoreConsole() {
        Object.keys(originalConsoleMethods).forEach(method => {
          console[method] = originalConsoleMethods[method];
        });
      }

      // 辅助函数：将输出追加到相应的类型中
      function appendOutput(type, message) {
        outputs.push({
          type: type,
          message: message,
          time: Date.now()
        });
      }


      // 辅助函数用于格式化参数
      function formatArguments(args) {
        return args.map(arg => {
          // 如果是 null 或者 undefined 直接返回它们的字符串表示
          if (arg === null || arg === undefined) {
            return String(arg);
          }

          // 检查是否为对象或数组，这些需要 JSON.stringify 来格式化
          if (typeof arg === 'object' && arg !== null) {
            try {
              return JSON.stringify(arg);
            } catch (e) {
              return '[无法序列化的对象]';
            }
          }

          // 对于原始类型，直接调用 toString 方法
          return arg.toString();
        }).join(' ');
      }

      // 重写 console 方法来捕获输出并保持原始输出
      console.log = (...args) => {
        // args.forEach(arg => appendOutput('log', String(arg)));
        appendOutput('log', formatArguments(args))
        originalConsoleMethods.log(...args);
      };

      console.info = (...args) => {
        // args.forEach(arg => appendOutput('info', String(arg)));
        appendOutput('info', formatArguments(args))
        originalConsoleMethods.info(...args);
      };

      console.warn = (...args) => {
        // args.forEach(arg => appendOutput('warn', String(arg)));
        appendOutput('warn', formatArguments(args))
        originalConsoleMethods.warn(...args);
      };

      console.error = (...args) => {
        // args.forEach(arg => appendOutput('error', String(arg)));
        appendOutput('error', formatArguments(args))
        originalConsoleMethods.error(...args);
      };

      console.table = (tabledata, tablecolumns) => {
        // 将表格数据推入 outputs.table 数组中
        // args.forEach(arg => outputs.table.push(JSON.stringify(arg)));
        let data = tabledata.map((item, index) => {
          if (tablecolumns) {
            return {
              index: index,
              [tablecolumns[index]]: item
            }
          }
          return {
            index: index,
            value: item
          }
        })
        appendOutput('table', JSON.stringify(data))
        originalConsoleMethods.table(tabledata, tablecolumns);
      };

      console.time = (label) => {
        originalConsoleMethods.time(label);
        times[label] = performance.now();
      };

      console.timeEnd = (label) => {
        const timeStart = times[label];
        if (timeStart !== undefined) {
          const duration = performance.now() - timeStart;
          appendOutput('log', `${label}: ${duration}ms`);
          delete times[label];
        }
        originalConsoleMethods.timeEnd(label);
      };

      // 设置全局错误处理器
      window.onerror = function (message, source, lineno, colno, error) {
        appendOutput('error', `Error: ${message} in ${source}:${lineno}:${colno}`);
        if (error) {
          appendOutput('error', error.stack);
        }
        // 同时打印到原始控制台
        console.error(`Error: ${message} in ${source}:${lineno}:${colno}`, error && error.stack);
        return true; // 防止默认行为
      };

      // 设置未捕获的 Promise 拒绝处理器
      window.onunhandledrejection = function (event) {
        appendOutput('error', `Uncaught Promise Rejection: ${event.reason}`);
        // 同时打印到原始控制台
        console.error(`Uncaught Promise Rejection:`, event.reason);
      };

      // 尝试执行用户代码
      try {
        eval(userCode);
      }
      catch (error) {
        appendOutput('error', `Execution Error: ${error.message}<br>${error.stack}`);
        // 同时打印到原始控制台
        console.error(`Execution Error: ${error.message}`, error.stack);
      }
      finally {
        restoreConsole()
      }

      // 打印所有输出到页面
      const outputDiv = document.getElementById('output');
      let htmlData = outputs.sort((a, b) => a.time - b.time)
      let html = ''
      html += htmlData.map((item, index) => {
        if (item.type === 'table') {
          let index = `<div class="item">index</div>`
          let value = `<div class="item">value</div>`
          let data = JSON.parse(item.message)
          for (let i of data) {
            index += `<div class="item">${i.index}</div>`
            value += `<div class="item">${i.value}</div>`
          }
          return `<div class="card">
              <div class="card__data">
                <div class="card__right">
                  ${index}
                </div>
                <div class="card__left">
                  ${value}
                </div>
              </div>
            </div>`
        } else {
          return `<div class="${item.type}">${item.message}</div>`;
        }
      }).join('');
      outputDiv.innerHTML = html;
    })();
</script>
</html>